package com.ibm.rules;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;

import com.google.common.base.Preconditions;
import com.google.common.collect.Range;
import com.ibm.interfaces.ITimeSeriesSchema;
import com.ibm.util.DataTypes;

import net.sf.jsqlparser.expression.BinaryExpression;
import net.sf.jsqlparser.expression.DoubleValue;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.ExpressionVisitorAdapter;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.Parenthesis;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.GreaterThan;
import net.sf.jsqlparser.expression.operators.relational.GreaterThanEquals;
import net.sf.jsqlparser.expression.operators.relational.MinorThan;
import net.sf.jsqlparser.expression.operators.relational.MinorThanEquals;
import net.sf.jsqlparser.expression.operators.relational.NotEqualsTo;

public final class RuleExpressionVisitor extends ExpressionVisitorAdapter {
	private List<RuleBuilder> rules;
	private BigInteger ruleId;
	private ITimeSeriesSchema tags;

	static private final int MAX_RULE_NUM = 30;

	public RuleExpressionVisitor(ITimeSeriesSchema tags, BigInteger ruleId) {
		this.ruleId = ruleId;
		this.tags = tags;
		this.rules = new ArrayList<>();
	}

	@Override
	protected void visitBinaryExpression(BinaryExpression expr) {
		visitBinaryExpression(expr, null);
	}

//	protected void visitRuleExpression(Expression expr, RuleBuilder rule) {
//		Preconditions.checkArgument(expr instanceof BinaryExpression
//				|| expr instanceof Parenthesis, "do not support "
//				+ expr.getClass().toString());
//
//		if (expr instanceof BinaryExpression) {
//			visitBinaryExpression((BinaryExpression) expr, rule);
//		} else if (expr instanceof Parenthesis) {
//			visitParenthesis((Parenthesis) expr, rule);
//		}
//	}
	
	protected void visitRuleExpression(Expression expr, RuleBuilder rule, boolean flag) {
		Preconditions.checkArgument(expr instanceof BinaryExpression
				|| expr instanceof Parenthesis, "do not support "
				+ expr.getClass().toString());

		if (expr instanceof BinaryExpression) {
			BinaryExpression tmp_expr = (BinaryExpression)expr;
			if (flag) {
				tmp_expr.setNot();
			}
			visitBinaryExpression(tmp_expr, rule);
		} else if (expr instanceof Parenthesis) {
			Parenthesis tmp_expr = (Parenthesis) expr;
			if (flag || tmp_expr.isNot()) {
				tmp_expr.setNot();
			}
			visitParenthesis(tmp_expr, rule);
		}
	}

	protected void visitParenthesis(Parenthesis parenthesis, RuleBuilder rule) {
		visitRuleExpression(parenthesis.getExpression(),rule, parenthesis.isNot());
	}

	
	//add by gxj to deal with not expression
	protected void visitOrExpression(BinaryExpression expr,RuleBuilder rule,boolean isNot) {

		RuleBuilder newRule = new RuleBuilder(rule);
		rules.add(newRule);

		/* early abandon */
		Preconditions
				.checkArgument(rules.size() <= MAX_RULE_NUM,
						"the condition is too complicated, just to refine it. ");
		visitRuleExpression(expr.getLeftExpression(), rule,isNot);
		visitRuleExpression(expr.getRightExpression(), newRule,isNot);
	
	}
	
	protected void visitAndExpression(BinaryExpression expr, RuleBuilder rule,boolean isNot) {
		visitRuleExpression(expr.getLeftExpression(), rule ,isNot);
//		visitRuleExpression(expr.getRightExpression(), rule, isNot);
		// this code is dangerous.
		int size = rules.size();
		for (int i = 0; i < size; i++) {
			RuleBuilder _rule = rules.get(i);
			visitRuleExpression(expr.getRightExpression(), _rule, isNot);
		}
	}
	
	protected void visitBinaryExpression(BinaryExpression expr, RuleBuilder rule) {
		if (rule == null) {
			rule = new RuleBuilder(ruleId);
			rules.add(rule);
		}
		if (expr instanceof AndExpression || expr instanceof OrExpression) {
			if (expr instanceof AndExpression) {
				if (expr.isNot()) {
					visitOrExpression(expr, rule, true);
				}else {
					visitAndExpression(expr, rule,false);
				}

			} else if (expr instanceof OrExpression) {
				if (expr.isNot()) {
					visitAndExpression(expr, rule, true);
				}else {
					visitOrExpression(expr, rule, false);				
				}
			}
		} else {
			// fixed by gxj. now support not expression
//			Preconditions.checkArgument(!expr.isNot(),
//					"do not support NOT for " + expr.getClass().toString());
			
			// swap the expression to make value in the right position
			Expression tagExpr = expr.getLeftExpression();
			Expression constantExpr = expr.getRightExpression();
			if (!(constantExpr instanceof LongValue)
					&& !(constantExpr instanceof DoubleValue)) {
				Expression tmp = tagExpr;
				tagExpr = constantExpr;
				constantExpr = tmp;
			}

			int tagId = tags.getTagId(removeQuotes(tagExpr.toString()));
			Preconditions.checkArgument(tagId >= 0, "undefined tag name "
					+ tagExpr.toString());

			if (expr instanceof NotEqualsTo) {


				if (expr.isNot()) {
					RuleBuilder newRule = new RuleBuilder(rule);
					rules.add(newRule);
					EqualsTo eq = new EqualsTo();
					eq.setLeftExpression(tagExpr);
					eq.setRightExpression(constantExpr);
					visitRuleExpression(eq, newRule, false);
				}else {
					/* != C is interpreted as > C or < C */
					RuleBuilder newRule = new RuleBuilder(rule);
					rules.add(newRule);

					/* early abandon */
					Preconditions
							.checkArgument(rules.size() <= MAX_RULE_NUM,
									"the condition is too complicated, try to refine it. ");

					GreaterThan gt = new GreaterThan();
					gt.setLeftExpression(tagExpr);
					gt.setRightExpression(constantExpr);
					visitRuleExpression(gt, rule,false);

					MinorThan lt = new MinorThan();
					lt.setLeftExpression(tagExpr);
					lt.setRightExpression(constantExpr);
					visitRuleExpression(lt, newRule,false);
				}
				
			} else {
				// <=,>= ...
				if (constantExpr instanceof LongValue) {
					if (DataTypes.isFloatOrDouble(tags.getDataTypes()[tagId])) {
						rule.addCondition(
								tagId,
								getRange(
										expr,
										Long.valueOf(
												((LongValue) constantExpr)
														.getValue())
												.doubleValue(),expr.isNot()));
					} else {
						rule.addCondition(
								tagId,
								getRange(expr,
										((LongValue) constantExpr).getValue(),expr.isNot()));
					}
				} else if (constantExpr instanceof DoubleValue) {
					Preconditions
							.checkArgument(DataTypes.isFloatOrDouble(tags
									.getDataTypes()[tagId]),
									"Double constant value is specified for a tag with a integal type");
					rule.addCondition(
							tagId,
							getRange(expr,
									((DoubleValue) constantExpr).getValue(),expr.isNot()));
				} else {
					System.out.println(constantExpr.getClass().toString());
					throw new RuntimeException("cannot parse the condition "
							+ constantExpr.getClass().toString());
				}
			}

		}

	}
	
	private String removeQuotes(String tag){
		if (tag.charAt(0) == '\"' && tag.charAt(tag.length() - 1) == '\"') {
			return tag.substring(1, tag.length() - 1);
		}
		return tag;
	}

	private <T extends Comparable<T>> Range<T> getRange(BinaryExpression expr,
			T value) {
		if (expr instanceof GreaterThan) {
			return Range.openClosed(value, DataTypes.getMaxValue(value));
		} else if (expr instanceof GreaterThanEquals) {
			return Range.closed(value, DataTypes.getMaxValue(value));
		} else if (expr instanceof MinorThan) {
			return Range.closedOpen(DataTypes.getMinValue(value), value);
		} else if (expr instanceof MinorThanEquals) {
			return Range.closed(DataTypes.getMinValue(value), value);
		} else if (expr instanceof EqualsTo) {
			return Range.closed(value, value);
		} else {
			throw new RuntimeException("Unsupported conditions: "
					+ expr.getClass().toString());
		}
	}
	
	
	private <T extends Comparable<T>> Range<T> getRange(BinaryExpression expr,
			T value, boolean isNot) {
		// the binary expression may have not expression
		if (isNot) {
			// not express
			if (expr instanceof GreaterThan) {
				// > to <=
				return Range.closed(DataTypes.getMinValue(value), value);
			} else if (expr instanceof GreaterThanEquals) {
				// >= to <
				return Range.closedOpen(DataTypes.getMinValue(value), value);
			} else if (expr instanceof MinorThan) {
				// < to >=
				return Range.closed(value, DataTypes.getMaxValue(value));
			} else if (expr instanceof MinorThanEquals) {
				// <= to >
				return Range.openClosed(value, DataTypes.getMaxValue(value));
			} else {
				throw new RuntimeException("In not conditions, Unsupported conditions: " + expr.getClass().toString());
			} 
		}else {
			if (expr instanceof GreaterThan) {
				return Range.openClosed(value, DataTypes.getMaxValue(value));
			} else if (expr instanceof GreaterThanEquals) {
				return Range.closed(value, DataTypes.getMaxValue(value));
			} else if (expr instanceof MinorThan) {
				return Range.closedOpen(DataTypes.getMinValue(value), value);
			} else if (expr instanceof MinorThanEquals) {
				return Range.closed(DataTypes.getMinValue(value), value);
			} else if (expr instanceof EqualsTo) {
				return Range.closed(value, value);
			} else {
				throw new RuntimeException("Unsupported conditions: " + expr.getClass().toString());
			} 
		}
	}

	public Rule[] getRules() {
		int size = rules.size();
		Rule[] result = new Rule[size];
		for (int i = 0; i < size; i++) {
			result[i] = rules.get(i).build();
		}
		return result;
	}
}